using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;

using Atomic.Libraries;
using Atomic.Libraries.Mathematics;

namespace Atomic.Structures.Symmetries
{
	/// <summary>
	/// Space group determined using Spglib.
	/// </summary>
	[Serializable]
	[DebuggerDisplay("{DebuggerDisplay}")]
	public class SpaceGroup
	{
		// To obtain and install Spglib in the home directory:
		//
		// wget http://downloads.sourceforge.net/project/spglib/spglib/spglib-1.4/spglib-1.4.1.tar.gz
		// tar -xzvf spglib-1.4.1.tar.gz
		// cd spglib-1.4.1
		// ./configure --prefix=$HOME/local
		// make
		// make install
		//
		// Then add this to .bash_profile:
		//
		// export LD_LIBRARY_PATH=$HOME/local/lib:$LD_LIBRARY_PATH
		// export PATH=$HOME/bin:$PATH
		//
		// If using MonoDevelop from Debian it's necessary to create a local monodevelop script,
		// $HOME/bin/monodevelop, otherwise LD_LIBRARY_PATH will be overwritten:
		//
		// #!/bin/bash
		// source ~/.bash_profile
		// /usr/bin/monodevelop

		private Structure structure;
		private double precision;
		private ReadOnlyList<SpaceGroupSymmetryOperation> symmetryOperations;
		private string symbol;
		private int number;
		//private Structure primitiveStructure;

		public SpaceGroup(Structure structure)
			: this(structure, 1.0e-5)
		{
			// The output precision of VASP is 7 digits, so this number is chosen somewhat smaller.
		}

		public SpaceGroup(Structure structure, double precision)
		{
			this.structure = structure;
			this.precision = precision;
		}

		private static double[] TransformLattice(Lattice lattice)
		{
			return new double[] { lattice.A.PhysicalCoordinates[0], lattice.B.PhysicalCoordinates[0], lattice.C.PhysicalCoordinates[0], lattice.A.PhysicalCoordinates[1], lattice.B.PhysicalCoordinates[1], lattice.C.PhysicalCoordinates[1], lattice.A.PhysicalCoordinates[2], lattice.B.PhysicalCoordinates[2], lattice.C.PhysicalCoordinates[2] };
			//return new double[] { lattice.A.PhysicalCoordinates[0], lattice.A.PhysicalCoordinates[1], lattice.A.PhysicalCoordinates[2], lattice.B.PhysicalCoordinates[0], lattice.B.PhysicalCoordinates[1], lattice.B.PhysicalCoordinates[2], lattice.C.PhysicalCoordinates[0], lattice.C.PhysicalCoordinates[1], lattice.C.PhysicalCoordinates[2] };
		}

		private static int TransformSites(Lattice lattice, IEnumerable<Site> sites, out double[] positions, out int[] types)
		{
			Dictionary<int, Atom> reverseTypes;
			return TransformSites(lattice, sites, out positions, out types, out reverseTypes);
		}

		private static int TransformSites(Lattice lattice, IEnumerable<Site> sites, out double[] positions, out int[] types, out Dictionary<int, Atom> reverseTypes)
		{
			List<double> positions0 = new List<double>();
			List<int> types0 = new List<int>();
			Dictionary<Atom, int> atoms = new Dictionary<Atom, int>();
			reverseTypes = new Dictionary<int, Atom>();

			int n = 0;
			foreach (Site site in sites)
			{
				SpaceVector p = lattice.Decompose(site.Position).Coordinates;

				positions0.Add(p[0]);
				positions0.Add(p[1]);
				positions0.Add(p[2]);

				if (!atoms.ContainsKey(site.Atom))
				{
					int type = atoms.Count + 1;
					atoms.Add(site.Atom, type);
					reverseTypes.Add(type, site.Atom);
				}
				types0.Add(atoms[site.Atom]);

				n++;
			}

			positions = positions0.ToArray();
			types = types0.ToArray();
			return n;
		}

		[DllImport("symspg")]
		private static extern int spg_get_multiplicity(double[] lattice, double[] positions, int[] types, int atoms, double precision);

		[DllImport("symspg")]
		private static extern int spg_get_symmetry(int[] rotations, double[] translations, int size, double[] lattice, double[] positions, int[] types, int atoms, double precision);

		private static IEnumerable<SpaceGroupSymmetryOperation> SpgGetSymmetry(Structure structure, double precision)
		{
			if (structure.Sites.Count == 0)
			{
				return new SpaceGroupSymmetryOperation[0];
			}

			double[] lattice = TransformLattice(structure.Lattice);

			double[] positions;
			int[] types;
			int atoms = TransformSites(structure.Lattice, structure.Sites, out positions, out types);

			//int multiplicity = 480;
			int multiplicity = spg_get_multiplicity(lattice, positions, types, atoms, precision);

			int[] rotations = new int[9 * multiplicity];
			double[] translations = new double[3 * multiplicity];

			int n = spg_get_symmetry(rotations, translations, multiplicity, lattice, positions, types, atoms, precision);

			List<SpaceGroupSymmetryOperation> operations = new List<SpaceGroupSymmetryOperation>();
			for (int k = 0; k < n; k++)
			{
				double[,] rotation = new double[3, 3];
				double[] translation = new double[3];
				for (int i = 0; i < 3; i++)
				{
					for (int j = 0; j < 3; j++)
					{
						rotation[i, j] = rotations[9 * k + 3 * i + j];
					}
					translation[i] = translations[3 * k + i];
				}

				operations.Add(new SpaceGroupSymmetryOperation(structure.Lattice, new SpaceMatrix(rotation), new SpaceVector(translation)));
			}

			return operations;
		}

		[DllImport("symspg")]
		private static extern int spg_get_international(sbyte[] symbol, double[] lattice, double[] positions, int[] types, int atoms, double precision);

		private static void SpgGetInternational(Structure structure, double precision, out int number, out string symbol)
		{
			if (structure.Sites.Count == 0)
			{
				number = 0;
				symbol = null;
				return;
			}

			double[] lattice = TransformLattice(structure.Lattice);

			double[] positions;
			int[] types;
			int atoms = TransformSites(structure.Lattice, structure.Sites, out positions, out types);

			sbyte[] symbol0 = new sbyte[11];
			number = spg_get_international(symbol0, lattice, positions, types, atoms, precision);

			symbol = "";
			foreach (sbyte c in symbol0)
			{
				if (c == 0)
				{
					break;
				}
				symbol += (char)c;
			}
			symbol = symbol.Trim();
		}

		[DllImport("symspg")]
		private static extern int spg_find_primitive(double[] lattice, double[] positions, int[] types, int atoms, double precision);

		private static Structure SpgFindPrimitive(Structure structure, double precision)
		{
			if (structure.Sites.Count == 0)
			{
				return structure;
			}

			double[] lattice = TransformLattice(structure.Lattice);

			double[] positions;
			int[] types;
			Dictionary<int, Atom> reverseTypes;
			int atoms = TransformSites(structure.Lattice, structure.Sites, out positions, out types, out reverseTypes);

			int n = spg_find_primitive(lattice, positions, types, atoms, precision);

			Lattice primitiveLattice = new Lattice(Basis.StandardBasis, new SpaceVector(lattice[0], lattice[1], lattice[2]), new SpaceVector(lattice[3], lattice[4], lattice[5]), new SpaceVector(lattice[6], lattice[7], lattice[8]));
			List<Site> primitiveSites = new List<Site>();
			for (int i = 0; i < n; i++)
			{
				primitiveSites.Add(new Site(reverseTypes[types[i]], new Position(primitiveLattice, positions[3 * i], positions[3 * i + 1], positions[3 * i + 2])));
			}

			return new Structure(primitiveLattice, primitiveSites);
		}

		public ReadOnlyList<SpaceGroupSymmetryOperation> SymmetryOperations
		{
			get
			{
				if (symmetryOperations == null)
				{
					symmetryOperations = new ReadOnlyList<SpaceGroupSymmetryOperation>(SpgGetSymmetry(structure, precision));
				}

				return symmetryOperations;
			}
		}

		public int Multiplicity
		{
			get
			{
				// Implement directly!
				return SymmetryOperations.Count;
			}
		}

		/// <summary>
		/// The space group number.
		/// </summary>
		public int Number
		{
			get
			{
				if (symbol == null)
				{
					SpgGetInternational(structure, precision, out number, out symbol);
				}

				return number;
			}
		}

		/// <summary>
		/// The international symbol for the space group.
		/// </summary>
		public string Symbol
		{
			get
			{
				if (symbol == null)
				{
					SpgGetInternational(structure, precision, out number, out symbol);
				}

				return symbol;
			}
		}

		/*public Structure PrimitiveStructure
		{
			get
			{
				if (primitiveStructure == null)
				{
					primitiveStructure = SpgFindPrimitive(structure, precision);
				}

				return primitiveStructure;
			}
		}*/

		private string DebuggerDisplay
		{
			get
			{
				return string.Format("Number = {0}, Symbol = {1}", Number, Symbol);
			}
		}
	}
}
